/**
 */
package br.org.bertol.mestrado.engine.optimisation.aco;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.Verify;
import br.org.bertol.mestrado.engine.Objective;
import br.org.bertol.mestrado.engine.exception.HeuristicsException;
import br.org.bertol.mestrado.engine.optimisation.AbstractMetaHeuristica;
import br.org.bertol.mestrado.engine.optimisation.entity.Ant;
import br.org.bertol.mestrado.engine.optimisation.moo.ParetoDominace;
import br.org.bertol.mestrado.engine.optimisation.pso.PsoRepository;
import br.org.bertol.mestrado.engine.optimisation.sls.IteratedLocalSearch;

/**
 * @author contaqualquer
 */
public class AntColonyOptimization extends AbstractMetaHeuristica<Ant> {

    /**
     * Matriz de feromônios para cada objetivo.
     */
    private final transient EnumMap<Objective, Double[][]>         pheromones;

    /**
     * Taxa de evaporação.
     */
    private final transient float                                  fEvaporation;

    /**
     * Taxa mínima de feromônios.
     */
    private final transient float                                  fMinPheromone;

    /**
     * Respositório local de formigas.
     */
    private final transient AcoRepository                          acoRepository;

    /***/
    private final transient int                                    PLUS_15 = 15;

    /***/
    private final transient int                                    PLUS_10 = 10;

    /***/
    private final transient int                                    PLUS_5  = 5;

    /***/
    private final transient EnumMap<Objective, Number>             c1MelhorIteracaoSet;

    /***/
    private final transient EnumMap<Objective, ArrayList<Integer>> s1MelhorIteracaoSet;

    /***/
    private final transient EnumMap<Objective, Number>             c2MelhorIteracaoSet;

    /***/
    private final transient EnumMap<Objective, ArrayList<Integer>> s2MelhorIteracaoSet;

    /**
     * @param veri
     *            Objeto verify
     * @param numIter
     *            número de iterações
     * @param ants
     *            lista de formigas
     * @param evaporation
     *            Taxa de evaporação do feromônio
     * @param minPheromone
     *            Valor mínimo de feromônios
     */
    public AntColonyOptimization(final Verify veri, final long numIter,
        final List<Ant> ants, final float evaporation,
        final float minPheromone) {

        super(veri, numIter, ants);

        pheromones = new EnumMap<Objective, Double[][]>(Objective.class);

        for (Objective objective : super.verify.getObjectives()) {

            final Double[][] pheronome = new Double[verify.getClasses().
                    size()][verify
                    .getClasses().size()];

            for (Double[] ds : pheronome) {
                Arrays.fill(ds, 1.0);
            }

            // inicia matriz de feromônios
            pheromones.put(objective, pheronome);

        }

        fEvaporation = evaporation;

        fMinPheromone = minPheromone;

        acoRepository = new AcoRepository();

        c1MelhorIteracaoSet = new EnumMap<Objective, Number>(
                Objective.class);

        s1MelhorIteracaoSet = new EnumMap<Objective, ArrayList<Integer>>(
                Objective.class);

        c2MelhorIteracaoSet = new EnumMap<Objective, Number>(
                Objective.class);

        s2MelhorIteracaoSet = new EnumMap<Objective, ArrayList<Integer>>(
                Objective.class);

        for (double j = 0, aux = Math.random(); j < verify
                .getObjectives().length; j++, aux = 1 - aux) {

            final Objective objective =
                    verify.getObjectives()[(int) j];

            c1MelhorIteracaoSet.put(objective, Integer.MAX_VALUE);

            s1MelhorIteracaoSet.put(objective, new ArrayList<Integer>());

            c2MelhorIteracaoSet.put(objective, Integer.MAX_VALUE);

            s2MelhorIteracaoSet.put(objective, new ArrayList<Integer>());

        }
    }

    /**
     * {@inheritDoc}
     * @see br.org.bertol.mestrado.engine.optimisation.Tecninca#evaluate()
     */
    @Override
    public final void evaluate() throws HeuristicsException {

        try {

            final List<Integer> startClasses = verify.getStartClasses();

            for (int i = 0; i < numIteracoes; i++) {

                for (final Ant ant : listaPareto) {

                    // zera dados da formiga
                    ant.reset();

                    final Double startClass = startClasses.size()
                            * Math.random();

                    // formiga constroi o caminho
                    ant.doPath(startClasses.get(startClass
                            .intValue()), verify, pheromones);

                    // atualização da matriz de feromônios
                    for (Objective objective : verify.getObjectives()) {

                        // pega matriz de feromônios
                        final Double[][] temp = pheromones.get(objective);

                        // atualiza as posições com base no caminho encontrado

                        for (int pointer = 0; pointer < ant.getRoute().size() - 1; pointer++) {

                            final int coluna = ant.getRoute().get(pointer);

                            final int linha = ant.getRoute().get(pointer + 1);

                            temp[coluna - 1][linha - 1] = ((1 - fEvaporation)
                                    * temp[coluna - 1][linha - 1])
                                    + (fEvaporation * fMinPheromone);

                        }
                    }

                    // Aplica ILS
                    final Ant betterAnt = applyILS(ant);

                    // calcula melhores iterações
                    // para auxílio na atualização do feromônio
                    updateBestIterations(betterAnt);

                    // atualiza feromônios
                    updateGlobalPheromone();

                }

                acoRepository.addInto(listaPareto);

            }
        } catch (CloneNotSupportedException e) {
            throw new HeuristicsException(e);
        } catch (Exception e) {
            throw new HeuristicsException(e);
        }
    }

    /**
     * Realiza atualização global dos feromônios.
     */
    private void updateGlobalPheromone() {

        // atualização global de feromônios
        for (int linha = 0; linha < verify.getClasses()
                .size(); linha++) {

            for (int coluna = 0; coluna < verify.getClasses()
                    .size(); coluna++) {

                for (Objective objective : verify.getObjectives()) {

                    final ArrayList<Integer> tlist1 = s1MelhorIteracaoSet
                            .get(objective);

                    final ArrayList<Integer> tlist2 = s1MelhorIteracaoSet
                            .get(objective);

                    final Double[][] tmp = pheromones
                            .get(objective);

                    tmp[linha][coluna] *= (1 - fEvaporation);

                    if (tmp[linha][coluna] < fMinPheromone) {
                        tmp[linha][coluna] = Double
                                .valueOf(fMinPheromone);
                    }
                    if (tlist1.indexOf(linha + 1) + 1 < verify
                            .getClasses().size()
                            &&
                            tlist2.indexOf(linha + 1) + 1 < verify
                            .getClasses().size()
                            && tlist1
                            .get(tlist1.indexOf(linha + 1) + 1) == coluna + 1
                            && tlist2
                            .get(tlist2.indexOf(linha + 1) + 1) == coluna + 1) {
                        tmp[linha][coluna] += PLUS_15;
                    } else if (tlist1.indexOf(linha + 1) + 1 < verify
                            .getClasses().size()
                            && tlist1
                            .get(tlist1.indexOf(linha + 1) + 1) == coluna + 1) {
                        tmp[linha][coluna] += PLUS_10;
                    } else if (tlist2.indexOf(linha + 1) + 1 < verify
                            .getClasses().size()
                            && tlist2
                            .get(tlist2.indexOf(linha + 1) + 1) == coluna + 1) {
                        tmp[linha][coluna] += PLUS_5;
                    }
                }

            }
        }

    }

    /**
     * Atualiza as melhores iterações de cada objetivo.
     * @param ant
     *            Formiga
     */
    @SuppressWarnings("unchecked")
    private void updateBestIterations(final Ant ant) {
        for (final Objective objective : verify.getObjectives()) {

            if (ant.getHeight().get(objective).intValue() < c1MelhorIteracaoSet
                    .get(objective).intValue()) {

                // atualiza pesos
                c1MelhorIteracaoSet.put(objective, ant
                        .getHeight().get(objective).intValue());

                c2MelhorIteracaoSet.put(objective,
                                        c1MelhorIteracaoSet
                        .get(objective)
                        .intValue());

                // atualiza caminhos
                s1MelhorIteracaoSet
                        .put(objective,
                             (ArrayList<Integer>) ant
                        .getRoute().clone());

                s2MelhorIteracaoSet
                        .put(
                             objective,
                             (ArrayList<Integer>) s1MelhorIteracaoSet
                        .get(objective).clone());
            } else if (ant.getHeight().get(objective)
                    .intValue() < c2MelhorIteracaoSet
                    .get(objective).intValue()) {

                // atualiza caminhos
                s2MelhorIteracaoSet
                        .put(objective,
                             (ArrayList<Integer>) ant
                        .getRoute().clone());

                // atualiza pesos
                c2MelhorIteracaoSet.put(objective, ant
                        .getHeight().get(objective).intValue());

            }

        }
    }

    /**
     * Aplica busca ILS.
     * @param ant
     *            Partícula a ser melhorada
     * @return Partícula melhorada
     * @throws Exception
     *             Erro
     */
    private Ant applyILS(final Ant ant) throws Exception {

        Logger.getLogger(getClass())
                .debug(
                       "Unidade pré ILS ---------------------------- : "
                + ant);

        final IteratedLocalSearch<Ant> ils = new IteratedLocalSearch<Ant>(
                verify, ant, 10);

        ils.evaluate();

        final Ant newAnt = ils.getBetterUnit();

        Logger.getLogger(getClass())
                .debug(
                       "Unidade pós ILS ---------------------------- : "
                + newAnt);

        if (paretoDominace.compare(newAnt.getHeight(), ant
                .getHeight()) == ParetoDominace.LEFT_DOMINE) {
            newAnt.update();
        }

        return newAnt;
    }

    /**
     * Retorna o swarm.
     * @return {@link List}
     */
    public final List<Ant> getSwarm() {
        return listaPareto;
    }

    /**
     * @see PsoRepository#getParetoBorder()
     * @return Lista de soluções não dominadas
     */
    public final List<Ant> getParetoBorder() {
        return acoRepository.getParetoBorder();
    }
}
